概述
通过上几篇的学习,我们分析了并试验了ViewResolverComposite、BeanNameViewResolver和ContentNegotiatingViewResolver,这三个类都直接实现ViewResolver接口。Spring MVC提供了很多的ViewResolver实现,本章我们继续分析比较常用的几个视图解析器。
本系列文章是基于Spring5.0.5RELEASE。
AbstractCachingViewResolver
AbstractCachingViewResolver实现ViewResolver接口的抽象类,从类名可知,该类具有缓存功能,即缓存解析过的视图View对象,后续需要视图解析时,会先从缓存中查找,如果找到对应的视图就直接返回,如果未找到就创建一个视图对象放入缓存Map中,并返回创建对象。从其实现原理上来看,此类视图解析器的性能是最佳的。
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
/** 缓存视图map的初始大小 */
public static final int DEFAULT_CACHE_LIMIT = 1024;
/** 最大缓存数量 */
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
/** 解析过的View缓存容器,key是逻辑视图名,value是视图View对象 */
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);
/** 存储所有创建过的视图,本容器里的内容大于等于viewAccessCache中的内容
key是逻辑视图名,value是视图View对象 */
private final Map<Object, View> viewCreationCache =
new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
// 本容器内数量大于缓存数量时,移除最老的对象
if (size() > getCacheLimit()) {
// 移除缓存容器中最老的View对象
viewAccessCache.remove(eldest.getKey());
return true;
}
else {
return false;
}
}
};
/** 实现ViewResolver接口resolveViewName方法 */
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 缓存开关(默认开启)。通过属性cacheLimit的值控制,大于0开启缓存,小于0关闭缓存
if (!isCache()) {
return createView(viewName, locale);
}
else {
// 获取逻辑视图名称
Object cacheKey = getCacheKey(viewName, locale);
// 从缓存容器中查找缓存过的视图对象
View view = this.viewAccessCache.get(cacheKey);
// 缓存中未找到
if (view == null) {
synchronized (this.viewCreationCache) {
// 从创建过的视图容器中查找
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// 调用子类创建视图View对象
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
// 放入已访问缓存容器
this.viewAccessCache.put(cacheKey, view);
// 放入已创建视图容器
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
// 返回View对象
return (view != UNRESOLVED_VIEW ? view : null);
}
}
... ...
}
以上是AbstractCachingViewResolver的核心代码。简单说,本类就是实现了视图解析的缓存功能。
UrlBasedViewResolver
该类是ViewResolver接口的一种实现,并继承了AbstractCachingViewResolver抽象类,通过指定prefix前缀和suffix后缀,然后拼接逻辑视图名称加上前缀和后缀的方式确定视图URL。
UrlBasedViewResolver支持返回视图名称中包括redirect:前缀,以支持在客户端的跳转。比如当访问一个url"/demo",该url对应的handler返回的逻辑视图名为"redirect:/demo1",URLBasedViewResolver在创建视图时(createView方法中),判断逻辑视图名称的前缀是"redirect:"开头,接着裁剪掉"redirect:"前缀后,创建RedirectView对象,RedirectView对象将把请求返回的模型数据组合成查询参数形式拼接到redirect的URL后面,然后调用 HttpServletResponse 对象的 sendRedirect 方法进行重定向。(稍后我们实践验证)
同样的,URLBasedViewResolver还支持"forword:"前缀,然后封装成一个 InternalResourceView 对象,服务器端利用 RequestDispatcher 的 forword 方式跳转到指定的地址。
说了这么多,我们看下源码是如何实现的,如下:
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
... ...
/** 创建View实例 */
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// 处理redirect请求
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
// 去掉redirect:前缀
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
// 根据redirectUrl创建RedirectView实例
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(viewName, view);
}
// 处理forward请求
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
去掉forward:前缀
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
// 处理普通view(除redirect/forward)
// 调用父类createView方法,然后通过模板方法再调回本例的loadView方法
return super.createView(viewName, locale);
}
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
... ...
}
InternalResourceViewResolver
InternalResourceViewResolver继承UrlBasedViewResolver,故UrlBaseViewResolver具有的功能,InternalResourceViewResolver同样具备,在实际项目中也是使用最广泛的一种视图解析器。InternalResourceViewResolver会把返回的视图对象解析为InternalResourceView 对象,InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。代码如下:
public class InternalResourceViewResolver extends UrlBasedViewResolver {
... ...
/** 构造函数 */
public InternalResourceViewResolver() {
// 获取InternalResourceView
Class<?> viewClass = requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
setViewClass(viewClass);
}
@Override
protected Class<?> requiredViewClass() {
return InternalResourceView.class;
}
... ...
}
其他功能都与UrlBasedViewResolver一样。
实战练习
- UrlBaseViewResolver
Spring配置文件代码如下:
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<!-- 指定前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 指定后缀 -->
<property name="suffix" value=".jsp"/>
<!-- 是否使用缓存,大于0是表示启用 -->
<property name="cacheLimit" value="0"/>
</bean>
Controller代码如下:
@Controller
public class DemoController {
/** 测试redirect */
@GetMapping("/redirect")
public String redirect(ModelMap modelMap){
modelMap.put("name","daliang");
modelMap.put("pass","111");
return "redirect:/demo";
}
/** 测试forward */
@GetMapping("/forward")
public String forward(ModelMap modelMap){
modelMap.put("name","daliang");
modelMap.put("pass","111");
return "forward:/demo";
}
@GetMapping("/demo")
public String demo(){
return "test";
}
}
启动应用,在浏览器地址栏输入http://localhost:8088/redirect,回车后如下:
可见参数拼接到了url后面。
- InternalResourceViewResolver
此解析器与UrlBasedViewResolver差不多,更改下配置文件中的类全路径即可。
总结
本章介绍了AbstractCachingViewResolver、UrlBasedViewResolver以及InternalResourceViewResolver三个视图解析器。这部分内容有点儿多,我会尽快结束。希望能帮到大家,谢谢!
最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。